type Filter = ({
  // 是否启用过滤器
  enabledFilter?: false
} | {
  enabledFilter?: true,
  // 双二阶滤波器
  filter?: {
    type: BiquadFilterType // 过滤类型 lowpass
    frequency: number // 过滤频率
  },
});

type AudioRecorderOptions = Filter & {
  // 采样率
  sampleRate?: number
  // 比特率
  bitRate?: number,
  // 媒体类型
  mimeType?: string
  analyser?: {
    enabled?: boolean
    fftSize?: number
  }
  // onSuccess?: () => void
  // onError?: (err: any) => void
  // onStart?: (event: Event) => void
  // onStop?: (event: Event) => void
  // onPause?: (event: Event) => void
  // onResume?: (event: Event) => void
  // ondataavailable?: (event: BlobEvent) => void
}


export default class AudioRecorder {
  // 可视化的canvas
  canvas: HTMLCanvasElement | null = null
  // 可视化绘制上下文
  visualContext: CanvasRenderingContext2D | null = null
  // 录制配置参数
  options: AudioRecorderOptions = {
    sampleRate: 44100,
    bitRate: 128000,
    mimeType: 'audio/mp3',
    enabledFilter: false,
    analyser: {
      enabled: true,
      fftSize: 2048
    }
  }
  // 是否支持getUserMedia
  isSupport: boolean = false

  // 录制状态 录制中、暂停、结束
  recorderStatus: 'end' | 'pause' | 'recording' = 'end';

  // 录制音频流
  private stream: MediaStream | null = null;
  // recorder对象
  private recorder: MediaRecorder | null = null;
  // 音频上下文
  private audioContext: AudioContext | null = null;
  // 录制音频数据
  private chunks: Blob[] = [];

  // 过滤器
  private filter: BiquadFilterNode | null = null;
  // 分析器
  private analyser: AnalyserNode | null = null;
  // 分析器数据
  private analyserData: Uint8Array | null = null;

  // 生成的录音文件
  private file: File | null = null;

  constructor(options: AudioRecorderOptions)
  constructor(canvas: HTMLCanvasElement, options: AudioRecorderOptions)
  constructor(p1: HTMLCanvasElement | AudioRecorderOptions, p2: AudioRecorderOptions = {}) {
    let opts: AudioRecorderOptions = {}
    if(p1 instanceof HTMLCanvasElement) {
      this.canvas = p1
      this.visualContext = this.canvas.getContext('2d')
      opts = p2
    } else {
      opts = p1;
    }

    mergeProperties(this.options, opts)
    // Object.assign(this.options, opts)
    this.getUserMediaPermission();
  }

  onSuccess(): void { }
  onError(err: any): void { }
  onStart(event: Event): void { }
  onStop(event: Event): void { }
  onPause(event: Event): void { }
  onResume(event: Event): void { }
  onMuted(): void { }
  onUnmuted(): void { }
  ondataavailable(event: BlobEvent): void { }

  private init() {
    // 创建音频上下文 
    // @ts-ignore
    this.audioContext = new (window.AudioContext || window.webkitAudioContext)({ sampleRate: this.options.sampleRate })
    if (this.options.enabledFilter) {
      // 初始化过滤器参数
      this.options.filter = mergeProperties({
        type: 'lowpass' as BiquadFilterType,
        frequency: 2000
      }, this.options.filter || {});

      this.filter = this.audioContext.createBiquadFilter()
      this.filter.type = this.options.filter.type
      this.filter.frequency.value = this.options.filter.frequency

      this.audioContext.destination.connect(this.filter)
    }

    // 创建可视化接口
    if (this.canvas && this.options.analyser?.enabled) {
      this.analyser = this.audioContext.createAnalyser()
      this.analyser.fftSize = 2048
      this.analyserData = new Uint8Array(this.analyser.frequencyBinCount)
    } else if(!this.canvas && this.options.analyser?.enabled){
      console.error('开启音频可视化需要传入一个canvas元素')
    }
  }

  /**
   * 开始录制
   */
  public start() {
    if(!this.isSupport) {
      this.onError('getUserMedia is not supported')
      return
    }

    if (this.recorderStatus === 'pause') {
      this.resume()
      return
    }

    if (this.recorderStatus !== 'end') return; // 如果正在录制，则不执行

    // 清除上一个文件
    this.file = null;
    navigator.mediaDevices.getUserMedia({
      audio: true
    }).then(stream => {
      this.stream = stream

      if (this.filter) {
        this.audioContext!.destination.connect(this.filter)
      }

      if (this.options.analyser?.enabled && this.canvas) {
        this.analyser!.connect(this.audioContext!.destination)
        this.analyser!.getByteTimeDomainData(this.analyserData!)
      }

      this.recorder = new MediaRecorder(stream, {
        mimeType: this.options.mimeType,
        audioBitsPerSecond: this.options.bitRate,
      })

      this.recorder.start()
      
      this.recorder.ondataavailable = (event: BlobEvent) => {
        this.chunks.push(event.data)
        this.ondataavailable(event)
      }

      this.recorder.onstop = (event: Event) => {
        this.recorderStatus = 'end'

        this.generateFile()
        this.chunks = []
        this.stream?.getAudioTracks()[0].stop()
        this.onStop(event)
      }

      this.recorder.onpause = (event: Event) => {
        this.recorderStatus = 'pause'
        this.onPause(event)
      }

      this.recorder.onresume = (event: Event) => {
        this.recorderStatus = 'recording'
        this.onResume(event)
      }

      this.recorder.onerror = (event: Event) => {
        this.onError(event)
        this.recorderStatus = 'end'
        this.stop()
      }

      this.recorder.onstart = (event: Event) => {
        this.recorderStatus = 'recording'
        this.onStart(event)
      }
    })
  }

  /**
   * 暂停录制
   */
  public pause() {
    if(!this.isSupport) {
      this.onError('getUserMedia is not supported')
      return
    }

    if (this.recorderStatus === 'recording') {
      this.recorder!.pause()
    }
  }

  /**
   * 恢复录制
   */
  public resume() {
    if(!this.isSupport) {
      this.onError('getUserMedia is not supported')
      return
    }

    if (this.recorderStatus === 'pause') {
      this.recorder!.resume()
    }
  }

  /**
   * 结束录制
   */
  public stop() {
    if(!this.isSupport) {
      this.onError('getUserMedia is not supported')
      return
    }

    if (this.recorder) {
      this.recorder.stop()
    }
  }

  /**
   * 静音
   */
  public mute() {
    if(this.stream) {
      this.stream.getAudioTracks()[0].enabled = false
      this.onMuted()
    }
  }

  /**
   * 取消静音
   */
  public unmute() {
    if(this.stream) {
      this.stream.getAudioTracks()[0].enabled = true
      this.onUnmuted()
    }
  }

  /**
   * 生成音频文件
   */
  private generateFile() {
    const extname = this.options.mimeType?.split('/')[1] || 'mp3'
    let blob = new Blob(this.chunks, { type: this.options.mimeType })
    this.file = new File([blob], Date.now() + extname, {
      type: this.options.mimeType
    })
  }

  /**
   * 下载音频文件
   */
  public download() {
    if(this.file) {
      const url = URL.createObjectURL(this.file)
      const a = document.createElement('a')
      a.href = url
      a.download = this.file.name
      a.click()
      URL.revokeObjectURL(url)
    }
  }

  /**
   * 获取录制权限
   */
  private getUserMediaPermission() {
    if(window.navigator.mediaDevices.getUserMedia) {
      window.navigator.mediaDevices.getUserMedia({
        audio: true
      }).then(
        stream => {
          this.isSupport = true
          this.init()
          stream.getTracks()[0].stop();
          this.onSuccess()
        },
        error => {
          console.error(error)
          this.onError(error)
        }
      )
    } else {
      this.isSupport = false
      this.onError('getUserMedia is not supported')
    }
  }
}

function mergeProperties<T extends Record<string, any>, S extends Record<string, any>>(target: T, source: S): T & S {
  Object.keys(source).forEach(key => {
    if(target[key] === undefined) {
      target[key as keyof T] = source[key]
    } else {
      if(typeof source[key] === 'object') {
        mergeProperties(target[key], source[key])
      } else {
        target[key as keyof T] = source[key]
      }
    }
  })
  return target as T & S
}
